package model.entity;

import gui.item.ItemData;

import java.util.Date;

import model.entity.container.ProductContainer;
import model.management.StorageUnitManager;
import reports.visitor.IVisitor;

import common.Barcode;
import common.Result;
import common.util.DateUtils;

/**
 * Represents each purchased item that exists in inventory.
 * 
 * @author Trevor Dixon, Ian Ryu
 * 
 */
public class Item extends Entity
{
  private static final long serialVersionUID = -5336380052149876111L;

  private final Product     product;
  private final Barcode     barcode;
  private Date              entryDate;
  private Date              exitTime;
  private Date              expirationDate;
  private ProductContainer  container;
  private int               container_id;

  /**
   * Copy constructor.
   * 
   * @param other
   *          The other item that this new item will duplicate.
   */
  public Item(Item other)
  {
    product = other.product;
    barcode = other.barcode;
    entryDate = other.entryDate;
    exitTime = other.exitTime;
    expirationDate = other.expirationDate;
    container = other.container;
  }

  /**
   * Constructor.
   * 
   * @param product
   *          The type of product this item is.
   * @param barcode
   *          The unique barcode of this item.
   * @param entryDate
   *          The date this item was entered.
   * @param expirationDate
   *          The date this item will expire.
   * @param container
   *          The immediate container of this item.
   */
  public Item(Product product, Barcode barcode, Date entryDate, Date expirationDate,
      ProductContainer container)
  {
    this.product = product;
    this.barcode = barcode;
    this.entryDate = entryDate;
    this.expirationDate = expirationDate;
    this.container = container;
  }

  /**
   * @return The <code>Barcode</code> that identifies this item.
   */
  public Product getProduct()
  {
    return product;
  }

  /**
   * @return The <code>Barcode</code> that identifies this item.
   */
  public Barcode getBarcode()
  {
    return barcode;
  }

  /**
   * @return The <code>Date</code> on which this item was added to the system.
   */
  public Date getEntryDate()
  {
    return entryDate;
  }

  /**
   * @return The <code>Date</code> (and time) on which this item was removed from the system, or
   *         <tt>null</tt> if the item is still in the system.
   */
  public Date getExitTime()
  {
    return exitTime;
  }

  /**
   * @param exitTime
   *          The <code>Date</code> (including time) at which this item was removed from the
   *          system.
   * @return A <code>Result</code> indicating the success or failure of the operation.
   */
  public Result setExitTime(Date exitTime)
  {
    Result validExitTime = validateExitTime(exitTime);

    if (!validExitTime.getStatus())
    {
      return validExitTime;
    }

    this.exitTime = exitTime;
    return new Result(true);
  }

  /**
   * @param exitTime
   *          The <code>Date</code> (including time) to check for validity as a potential
   *          <code>exitTime</code> for the item.
   * @return A <code>Result</code> confirming or denying the validity of the supplied
   *         <code>Date</code>.
   */
  public Result validateExitTime(Date exitTime)
  {
    if (exitTime == null)
    {
      return new Result(false, "Exit Time is null/wrong format");
    }

    return new Result(true);
  }

  /**
   * @return The <code>Date</code> on which this item will (or already did) expire.
   */
  public Date getExpirationDate()
  {
    return expirationDate;
  }

  /**
   * @param expirationDate
   *          The <code>Date</code> on which this item will expire.
   * @return A <code>Result</code> indicating the success or failure of this operation.
   */
  public Result setExpirationDate(Date expirationDate)
  {
    Result validExpirationDate = validateExpirationDate(expirationDate);

    if (!validExpirationDate.getStatus())
    {
      return validExpirationDate;
    }

    this.expirationDate = expirationDate;
    return new Result(true);
  }

  /**
   * @param expirationDate
   *          The <code>Date</code> to check for validity as a potential expiration date for the
   *          item.
   * @return A <code>Result</code> confirming or denying the validity or the supplied expiration
   *         date.
   */
  public Result validateExpirationDate(Date expirationDate)
  {

    if (expirationDate == null)
    {
      return new Result(false, "Expiration Date is Null/wrong foramtted");
    }

    return new Result(true);
  }

  /**
   * @param productContainer
   * 
   * @return A <code>Result</code> confirming or denying the validity of the supplied
   *         <ProductContainer> as a potential parent container for the item.
   */
  public Result validateAddToProductContainer(ProductContainer container)
  {
    if (container == null)
    {
      return new Result(false, "Error: Null Item.");
    }

    return new Result(true);
  }

  /**
   * @param productContainer
   * 
   * @return A <code>Result</code> indicating whether the operation was successful.
   */
  public Result addToProductContainer(ProductContainer container)
  {
    Result validAdd = validateAddToProductContainer(container);
    if (!validAdd.getStatus())
    {
      return validAdd;
    }

    this.container = container;
    return new Result(true);
  }

  /**
   * @param productContainer
   * 
   * @return A <code>Result</code> indicating whether this item can be removed from the supplied
   *         <code>ProductContainer</code>.
   */
  public Result validateRemoveFromProductContainer(ProductContainer container)
  {
    if (container == null)
    {
      return new Result(false, "Error: Null Item.");
    }

    return new Result(true);
  }

  /**
   * @param productContainer
   * 
   * @return A result indicating whether the operation was successful.
   */
  public Result removeFromProductContainer(ProductContainer container)
  {
    Result validRemove = validateRemoveFromProductContainer(container);
    if (!validRemove.getStatus())
    {
      return validRemove;
    }

    this.container = container;
    return new Result(true);
  }

  /**
   * @return The parent <code>ProductContainer</code> of this item.
   */
  public ProductContainer getContainer()
  {
    return container;
  }

  /**
   * Sets the parent container of this item
   * 
   * @param container
   *          The new container
   * 
   *          {@pre container != null} {@post getContainer() == container}
   */
  public void setContainer(ProductContainer container)
  {
    Result result = this.container.removeItem(this);
    if (result.getStatus() == false)
    {
      System.err.println("Could not set item container: " + result.getMessage());
      throw new RuntimeException();
    }
    result = container.addItem(this);
    if (result.getStatus() == false)
    {
      System.err.println("Could not set item container: " + result.getMessage());
      throw new RuntimeException();
    }
    this.container = container;
  }

  public void setNewContainer(ProductContainer container)
  {
    this.container = container;
  }

  /**
   * @return an ItemData object that encapsulates the information of this item.
   */
  public ItemData generateItemDataObject()
  {
    ItemData data = new ItemData();

    data.setBarcode(barcode.getCode());
    data.setEntryDate(entryDate);
    data.setExpirationDate(expirationDate);
    data.setProductGroup(container.getName());
    data.setStorageUnit(StorageUnitManager.getStorageUnit(container).getName());
    data.setTag(this);

    return data;
  }

  public Result setEntryDate(Date entryDate)
  {
    if (entryDate == null)
      return new Result(false, "Cannot set entry date - supplied date was null");

    this.entryDate = entryDate;
    calculateExpirationDate();
    return new Result(true);
  }

  private void calculateExpirationDate()
  {
    expirationDate = DateUtils.addMonthsToDate(entryDate, getProduct().getShelfLife());
  }

  public int getContainerId()
  {
    return container_id;
  }

  public void setContainerId(int container_id)
  {
    this.container_id = container_id;
  }

  @Override
  public int hashCode()
  {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((barcode == null) ? 0 : barcode.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj)
  {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof Item))
      return false;
    Item other = (Item) obj;
    if (barcode == null)
    {
      if (other.barcode != null)
        return false;
    }
    else if (!barcode.equals(other.barcode))
      return false;
    return true;
  }

  @Override
  public String toString()
  {
    return "Item [barcode=" + barcode + "]";
  }

  public boolean isExpired()
  {
    Date today = DateUtils.currentDate();

    return getExpirationDate().compareTo(today) <= 0;
  }

  public boolean isRemoved()
  {
    return exitTime != null;
  }

  @Override
  public void preOrderAccept(IVisitor visitor)
  {
    visitor.visit(this);
  }

  @Override
  public void postOrderAccept(IVisitor visitor)
  {
    visitor.visit(this);
  }

}
